// -----------------------------------------------------------------------
//  <copyright file="DaprPlusBuilder.cs" company="LiuliuSoft">
//      Copyright (c) 2022-2024 DaprPlus. All rights reserved.
//  </copyright>
//  <site>https://dapr.plus</site>
//  <last-editor>郭明锋</last-editor>
//  <last-date>2024/12/13 22:49</last-date>
// -----------------------------------------------------------------------

using System.Diagnostics;
using System.Text;

using DaprPlus.AspNetCore;

using DaprPlus.Dependency;
using DaprPlus.Logging;
using DaprPlus.Properties;

namespace DaprPlus.Builder;

/// <summary>
/// DaprPlus构建器
/// </summary>
public class DaprPlusBuilder : IDaprPlusBuilder
{
    private ModuleBase[] _modules = [];

    /// <summary>
    /// 初始化一个<see cref="DaprPlusBuilder"/>类型的新实例
    /// </summary>
    public DaprPlusBuilder(IServiceCollection services)
    {
        Services = services;
        var configuration = services.GetConfiguration();
        if (configuration == null)
        {
            configuration = services.BuildConfiguration();
            services.TryAddSingleton<IConfiguration>(configuration);
        }
        Singleton<IConfiguration>.Instance = configuration;

        DaprPlusContext = services.GetOrAddSingletonInstance(() => new DaprPlusContext());

        if (!services.AnyServiceType(typeof(ILoggerFactory)))
        {
            services.AddLogging(opts =>
            {
#if DEBUG
                opts.SetMinimumLevel(LogLevel.Debug);
#else
                opts.SetMinimumLevel(LogLevel.Information);
#endif
            });
        }
    }

    /// <summary>
    /// 获取 服务集合
    /// </summary>
    public IServiceCollection Services { get; }

    /// <summary>
    /// 获取 DaprPlus上下文
    /// </summary>
    public DaprPlusContext DaprPlusContext { get; }

    /// <summary>
    /// 获取 加载的模块集合
    /// </summary>
    public ModuleBase[] Modules => _modules.ToArray();

    /// <summary>
    /// 从启动模块初始化应用程序
    /// </summary>
    public void Build<TStartupModule>() where TStartupModule : ModuleBase
    {
        Build(typeof(TStartupModule));
    }

    private void Build(Type startupModuleType)
    {
        if (_modules.Any(m => m.GetType() == startupModuleType))
        {
            return;
        }

        _modules = GetAllDependModules(startupModuleType);
        var logName = typeof(DaprPlusBuilder).FullName!;
        var sb = new StringBuilder();
        sb.AppendLine($"查找到{_modules.Length}个模块，加载顺序如下：");
        foreach (var module in _modules)
        {
            sb.AppendLine($"\t{module.GetDisplay()}");
        }

        Services.LogInformation(sb.ToString(), logName);

        var configuration = Singleton<IConfiguration>.Instance;
        DaprPlusContext.Init(_modules, configuration);

        //查找并注册自动依赖注入类型
        var tmp = Services.ToArray();
        Services.AddDependency(DaprPlusContext);
        Services.ServiceLogDebug(tmp, logName);
        Services.LogInformation(ResDaprPlus.Format_Init_Depend_GlobalSearch_Count.FormatWith(Services.Count - tmp.Length) + "\n", logName);

        foreach (var module in _modules)
        {
            var moduleType = module.GetType();
            var moduleName = moduleType.GetDescription();
            Services.LogInformation(ResDaprPlus.Format_Init_Depend_Module_Add.FormatWith(moduleName, moduleType.Name), logName);
            tmp = Services.ToArray();
            AddModule(Services, module);
            Services.ServiceLogDebug(tmp, moduleType.FullName!);
            Services.LogInformation(ResDaprPlus.Format_Init_Depend_Module_Service_Count.FormatWith(moduleName,
                moduleType.Name, Services.Count - tmp.Length) + "\n", logName);
        }
    }

    private static void AddModule(IServiceCollection services, ModuleBase module)
    {
        var type = module.GetType();
        var baseType = typeof(ModuleBase);

        if (!services.Any(m => m.Lifetime == ServiceLifetime.Singleton && m.ServiceType == baseType && m.ImplementationInstance?.GetType() == type))
        {
            module.AddServices(services);
        }
    }

    private static ModuleBase[] GetAllDependModules(Type rootType)
    {
        var types = new List<Type>() { rootType };
        types.AddRange(GetDependedModuleTypes(rootType));
        var exclusionTypes = types.SelectMany(GetExclusionModuleTypes).Distinct().ToList();
        types = types.Except(exclusionTypes).Distinct().ToList();
        List<Type> tmpTypes = types.Select(m => m.BaseType).Where(m => m is { IsAbstract: false }).ToList()!;
        types = types.Except(tmpTypes).Distinct().ToList();
        var modules = types.Select(m => (ModuleBase)Activator.CreateInstance(m)!)
            .OrderBy(m => m.Level).ThenBy(m => m.Order).ThenBy(m => m.GetType().FullName).ToArray();
        return modules;
    }

    private static Type[] GetDependedModuleTypes(Type moduleType)
    {
        var stack = new Stack<Type>(new[] { moduleType });
        var dependTypes = new List<Type>();
        while (stack.Count > 0)
        {
            var tmpType = stack.Pop();
            var dependAttrs = tmpType.GetAttributes<DependsOnModulesAttribute>();
            if (dependAttrs.Length == 0)
            {
                continue;
            }

            foreach (var dependAttr in dependAttrs)
            {
                var moduleTypes = dependAttr.DependedModuleTypes;
                if (moduleTypes.Length == 0)
                {
                    continue;
                }

                dependTypes.AddRange(moduleTypes);
                foreach (var type in moduleTypes)
                {
                    stack.Push(type);
                }
            }
        }

        return dependTypes.Distinct().ToArray();
    }

    private static Type[] GetExclusionModuleTypes(Type moduleType)
    {
        var stack = new Stack<Type>(new[] { moduleType });
        var exclusionTypes = new List<Type>();
        while (stack.Count > 0)
        {
            var tmpType = stack.Pop();
            var exclusionAttrs = tmpType.GetAttributes<ExclusionModulesAttribute>();
            if (exclusionAttrs.Length > 0)
            {
                exclusionTypes.AddRange(exclusionAttrs.SelectMany(m => m.ExclusionModuleTypes));
            }
            var dependAttrs = tmpType.GetAttributes<DependsOnModulesAttribute>();
            if (dependAttrs.Length == 0)
            {
                continue;
            }

            foreach (var dependAttr in dependAttrs)
            {
                var moduleTypes = dependAttr.DependedModuleTypes;
                if (moduleTypes.Length == 0)
                {
                    continue;
                }

                foreach (var type in moduleTypes)
                {
                    stack.Push(type);
                }
            }
        }

        return exclusionTypes.Distinct().ToArray();
    }


    /// <summary>
    /// DaprPlus框架初始化，适用于Web应用程序
    /// </summary>
    /// <param name="app">Web应用程序对象</param>
    /// <returns></returns>
    public async Task<WebApplication> UseDaprPlus(WebApplication app)
    {
        var provider = app.Services;
        await DaprPlusInternal(provider, async module =>
        {
            if (module is AspModuleBase aspModule)
            {
                await aspModule.UseModule(app);
            }
            else
            {
                await module.UseModule(provider);
            }
        });
        return app;
    }

    /// <summary>
    /// DaprPlus框架初始化，适用于非Web应用程序
    /// </summary>
    /// <param name="provider">服务提供者</param>
    /// <returns></returns>
    public async Task<IServiceProvider> UseDaprPlus(IServiceProvider provider)
    {
        await DaprPlusInternal(provider, async module => await module.UseModule(provider));
        return provider;
    }

    private async Task DaprPlusInternal(IServiceProvider provider, Func<ModuleBase, Task> useModuleAction)
    {
        var logger = provider.GetLogger(typeof(DaprPlusBuilder));
        try
        {
            logger.LogInformation(ResDaprPlus.String_Init_Framework_Start);

            // 输出初始化服务日志信息
            var startupLogger = provider.GetService<StartupLogger>();
            startupLogger?.Output(provider);

            // 执行DaprPlus模块的初始化
            var watch = Stopwatch.StartNew();
            logger.LogInformation(ResDaprPlus.Format_Init_Framework_Module_Count + "\n", _modules.Length);
            foreach (var module in _modules)
            {
                var type = module.GetType();
                var name = type.GetDescription();
                logger.LogInformation(ResDaprPlus.Format_Init_Module_Ing, name, type.FullName);
                var watch2 = Stopwatch.StartNew();
                await useModuleAction(module);
                watch2.Stop();
                logger.LogInformation(ResDaprPlus.Format_Init_Module_End + "\n", name, type.FullName, watch2.ElapsedMilliseconds);
            }

            watch.Stop();
            logger.LogInformation(ResDaprPlus.Format_Init_Framework_End + "\n", watch.Elapsed);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, ex.Message);
            throw;
        }
    }
}
